import java.util.ArrayList;


public class Game extends Thread{
	private static Game instance = null;
	private boolean gameIsRunning = false;
	private long startTime;
	private long endTime;
	private boolean tenSecondsSignalSent = false;
	private boolean timeIsUpSignalSent = false;
	private boolean closeSignalSent = false;
	private boolean gameHasEnded = false;

	private UDPConnection udpConnection;
	private long updateTime = 1000000000 / Settings.FPS; //Time in nanoseconds
	private long lastUpdateTime;
	private long lastFPSTime;
	private int fps;
	private float updateFactor;

	private Game() {
		// Exists only to defeat instantiation.
	}
	public static Game getInstance() {
		if(instance == null) {
			instance = new Game();
		}
		return instance;
	}
	public void stopGame(){
		gameIsRunning = false;
		if(udpConnection != null)
		{
			udpConnection.stopUDPConnection();
			udpConnection = null;
		}
		instance = null;
	}


	/**
	 * Start the game.
	 */
	public void startGame(){
		if(ConnectionHandler.getInstance().getAmountOfClients() >= Settings.minimumAmountOfPlayers){

			gameIsRunning = true;
			udpConnection = new UDPConnection();
			//Set starttime and endtime for this match.
			this.startTime = System.currentTimeMillis();
			this.endTime = startTime + ((long)Settings.matchDuration * 1000);

			this.start();
			Settings.state = "Ingame";
			System.out.println("The server has started the game.");
		}
		else System.out.println("A minimum of " + Settings.minimumAmountOfPlayers + " players is required to start the game.");
	}

	@Override
	public void run()
	{
		this.lastUpdateTime = System.nanoTime();
		while (gameIsRunning)
		{	
			// print fps
			float elapsedTime = (System.nanoTime() - lastUpdateTime) / 1000000000f;
			float fps = 1 / elapsedTime;
			this.lastUpdateTime = System.nanoTime();
			
			//Update game only when game has not ended yet.
			if(!gameHasEnded){
				updateGame();
				CheckForRespawns();
			}
			
			//update time even if game has ended, since actions are still required after that.
			CheckIfTimeIsUp();
			
			long calculationDuration = System.nanoTime() - this.lastUpdateTime;

			try
			{
				// Sleeptime = duration of a frame minus the duration of the calculations made while updating
				long sleepTime = (this.updateTime - calculationDuration)/1000000;

				if(sleepTime > 0)
					Thread.sleep(sleepTime);
			}
			catch (Exception e){}
		}
	}

	/**
	 * Checks if certain players should respawn, and if so, makes them respawn.
	 */
	private void CheckForRespawns()
	{
		long currentTime = System.nanoTime();
		ArrayList<Client> players = ConnectionHandler.getInstance().getActiveClients();
		int playerCount = players.size();
		for(int i = 0; i < playerCount; i++){
			Client player = players.get(i);
			if(!player.getPlayerData().isAlive()){ //check dead players
				if(currentTime >= (player.getPlayerData().getTimeOfDeath() + ((long)Settings.respawnTime * 1000000000))){
					System.out.println("Respawn " + player.getName());
					player.getPlayerData().respawn();
				}
			}
		}
	}

	/**
	 * Checks if the time is up and the match has ended.
	 */
	private void CheckIfTimeIsUp(){
		long currentTime = System.currentTimeMillis();
		if(!this.timeIsUpSignalSent)
		{
			long remainingTime = this.endTime - currentTime;
			if(remainingTime <= 0)
			{
				gameHasEnded = true;
				String bestPlayersData = generateWinnerData();
				System.out.println("Winners: " + bestPlayersData);
				ConnectionHandler.getInstance().sendToAllClients("51_" + bestPlayersData);
				this.timeIsUpSignalSent = true;
			}
			else if(!this.tenSecondsSignalSent){ //only if 10 seconds signal wasnt sent yet
				if(remainingTime <= 10000) //10 seconds
				{		
					ConnectionHandler.getInstance().sendToAllClients("52_");
					this.tenSecondsSignalSent = true;
				}
			}
		}
		else
		{
			long overdue = currentTime - this.endTime; //find milliseconds past endtime
			if(!this.closeSignalSent){
				if(overdue > ((long)Settings.autoShutdownDelay) * 1000){ //if shutdowndelay has been reached
					//ALL CLIENTS MUST CLOSE THE GAME has been reached
					//Send scores to Webservice
					WebConnection.getInstance().uploadScoreData();
					//Notify connected clients
					ConnectionHandler.getInstance().sendToAllClients("53_");
					this.closeSignalSent = true;
				}
			}
			else{ //close server{
				if(overdue > ((long)Settings.autoShutdownDelay + 1) * 1000){ //if shutdowndelay plus one second has been reached
					//RESTART SERVER
					System.out.println("Match closed. Server restart initiated.");
					stopGame();
					ConnectionHandler.getInstance().reset();
				}
			}
		}
	}

	/**
	 * Creates a string for a TCP message containing the winners.
	 * @return
	 */
	private String generateWinnerData(){
		//find best player(s)
		ArrayList<Client> bestPlayers = getPlayersWithBestScore();
		//Create a string
		String bestScoresString = "";
		for(int i = 0; i < bestPlayers.size(); i++){
			if(i > 0)
				bestScoresString += "#";
			bestScoresString += bestPlayers.get(i).getClientId();
		}
		return bestScoresString;
	}

	/**
	 * Generates a list of the player(s) with the highest score.
	 * @return
	 */
	private ArrayList<Client> getPlayersWithBestScore(){
		ArrayList<Client> bestPlayers = new ArrayList<Client>();
		ArrayList<Client> players = ConnectionHandler.getInstance().getActiveClients();

		int bestFoundScore = -1;
		int playerCount = players.size();

		for(int i = 0; i < playerCount; i++){
			Client player = players.get(i);
			int score = player.getPlayerData().getScore();
			if(score == bestFoundScore){ 
				//shared first place so far
				bestPlayers.add(player);
				bestFoundScore = score;
			}
			else if(score > bestFoundScore){
				//Score is even better
				bestPlayers.clear();
				bestPlayers.add(player);
				bestFoundScore = score;
			}
		}

		return bestPlayers;
	}

	private void updateFPS(long updateLength){
		lastFPSTime += updateLength;
		fps++;

		// update our FPS counter if a second has passed since
		// we last recorded
		if (lastFPSTime >= 1000000000)
		{
			lastFPSTime = 0;
			fps = 0;
		}
	}

	/**
	 * Updates the entire game, executing calculations for all clients and their projectiles.
	 */
	private void updateGame(){
		ArrayList<Client> allClients = ConnectionHandler.getInstance().getActiveClients(); //Dont update disconnected clients
		for(int i = 0; i < allClients.size(); i++){
			//allClients.get(i).getPlayerData().updateData(updateFactor); //calculate changes in the player's data.
			allClients.get(i).getPlayerData().update(); //calculate changes in the player's data.
		}
	}
}
